Mediator Pattern

3 min read
Mid-level2 min read
Rapid overview

Mediator Pattern

TL;DR

Define an object that encapsulates how a set of objects interact. The mediator promotes loose coupling by keeping objects from referring to each other explicitly.

How it works

!alt text


🧩 Example β€” Mediator coordinating order validation, risk and execution

public interface ITradeMediator
{
    void Notify(object sender, string ev, Order order);
}

public class TradeMediator : ITradeMediator
{
    private readonly OrderValidator _validator;
    private readonly RiskService _risk;
    private readonly TradeExecutor _executor;

    public TradeMediator(OrderValidator validator, RiskService risk, TradeExecutor executor)
    {
        _validator = validator;
        _risk = risk;
        _executor = executor;
    }

    public void Notify(object sender, string ev, Order order)
    {
        if (ev == "PlaceOrder")
        {
            if (!_validator.Validate(order))
            {
                Console.WriteLine("Validation failed");
                return;
            }

            if (!_risk.Check(order))
            {
                Console.WriteLine("Risk check failed");
                return;
            }

            _executor.Execute(order);
        }
    }
}

public class OrderValidator
{
    private readonly ITradeMediator _mediator;
    public OrderValidator(ITradeMediator mediator) => _mediator = mediator;
    public bool Validate(Order order) => order != null && order.Amount > 0;
}

public class RiskService
{
    private readonly ITradeMediator _mediator;
    public RiskService(ITradeMediator mediator) => _mediator = mediator;
    public bool Check(Order order) => order.Amount <= 100000; // simple rule
}

public class TradeExecutor
{
    private readonly ITradeMediator _mediator;
    public TradeExecutor(ITradeMediator mediator) => _mediator = mediator;
    public void Execute(Order order) => Console.WriteLine($"Executed {order.Id} for {order.Amount}");
}

// --- Usage ---
// Compose mediator with concrete components
// var mediator = new TradeMediator(new OrderValidator(null), new RiskService(null), new TradeExecutor(null));
// Wire mediator into components and use mediator.Notify(this, "PlaceOrder", order);

βœ… Why it matters:

  • Centralizes interaction logic between components (validation, risk, execution).
  • Reduces direct dependencies between components, improving testability and maintainability.
  • Makes it easier to change coordination rules without touching each component.

Quick recall Q&A

Q: What problem does the Mediator pattern solve?

It avoids spaghetti dependencies among collaborating components (validator, risk, executor) by centralizing communication in a mediator. Components interact via the mediator instead of referencing each other directly.

Q: How does MediatR implement the Mediator pattern in .NET?

MediatR routes requests (commands/queries/notifications) to their handlers through a central mediator, letting senders remain unaware of receivers. Pipeline behaviors provide cross-cutting features without tight coupling.

Q: When would you build a custom mediator vs using MediatR?

Use MediatR for request/response flows in application layers. Build custom mediators when orchestrating domain services with bespoke protocols or when you need full control over orchestration semantics.

Q: How do mediators improve testability?

Components depend only on the mediator interface, so tests can supply stub mediators to assert interactions. You can validate coordination logic by testing the mediator in isolation.

Q: What’s a downside of mediators?

The mediator can become a god object if it accumulates too much logic. Mitigate by splitting mediators per feature or layering policies/pipeline behaviors to keep responsibilities focused.

Q: How do you prevent mediator logic from duplicating domain rules?

Keep mediators focused on coordination (who to notify next) and delegate business invariants to domain services/entities. If logic belongs to the domain, move it there rather than hiding it in the mediator.

Q: How do you handle asynchronous workflows with a mediator?

Define async methods (Task NotifyAsync(...)) and await dependent operations. MediatR supports async handlers out of the box, ensuring non-blocking orchestration.

Q: What patterns complement mediators?

Combine with CQRS (commands/queries flow through the mediator), decorators (pipeline behaviors like logging/validation), and event sourcing (mediator publishes domain events).

Q: How can mediators support extensibility?

Register handlers or collaborators via DI so new behaviors can be added without changing existing code. For example, add a compliance handler to the notification pipeline without modifying producer services.

Q: How do you monitor mediator pipelines?

Instrument pipeline behaviors or interceptors to log request duration, handler outcomes, and errors. This keeps observability centralized and avoids duplicating logging in every handler.

See also